查看原文
其他

当我们讨论机器学习平台时,我们在讨论什么

The following article is from 任如意外的小茶馆 Author 任如意外

概论

近年来飞速发展的机器学习,尤其以深度学习为代表,已发展成算力、数据、算法交相辉映的好看风景。如果要给由机器学习带来的算力技术找一个近似的比较物,那应该是高性能计算(HPC),两者都需要处理海量的计算和数据,机器学习也继承了HPC多年来积累的多项技术。

然而,传统的HPC只是少部分科研人员和研究机构才参与的,而机器学习搭上了AI普惠时代班车,迅速席卷了几乎所有公司和工程领域。所以,机器学习的工具箱越来越丰富,很快就超过了HPC的发展,例如:

  • Nvidia的CUDA将GPU引入通用计算领域

  • 传统的Python数据分析开始出现Numpy/Pandas/Scikit-Learn

  • Caffe/Theno/Torch等深度学习框架的出现

  • Tensorflow/Pytorch等设计更现代更成熟的框架

  • 分布式训练的多项技术,Keras/Digits等更上层的框架

  • CuML/Rapids等原生支持GPU的各层级的机器学习加速库...

关于机器学习的抽象脚步不止于此,这两年基于云服务的机器学习平台和框架如雨后春笋一样出现,提供使用者更加简便易用的体验,工程设施和框架反过来也极大促进了算法的发展。云和机器学习相结合有着很大的想象空间,因为机器学习往往需要巨大和昂贵的算力、海量的数据存储、极高的分布式通信性能要求等,而云服务的规模效应能够较大降低满足这些需求的成本,用户解脱出来专注于算法和业务的开发。此外,机器学习平台可以提供更全周期的服务,例如海量数据存储、ELK、数据增广、模型训练、模型发布、模型评估、算力扩容等等功能。

机器学习平台也有其自身的特殊性,例如分布式训练对节点间通信性能要求极高,CPU\GPU密集性的计算,海量的数据存储,极高的IO会对数据缓存策略有要求等。当然,按照我们的经验,还有传统用户对上云后的使用习惯的缺乏,所以也要提供非常好用的用户交互方式。

本文会重点介绍机器学习平台最常见的架构:基于Kubernetes的GPU容器调度架构的机器学习平台,典型的如IBM的FfDL的架构,我们会进行基本的介绍。此外,带着机器学习平台设计常见的难点问题,我们会介绍我们帆一尚行的机器学习平台iGear的架构、设计、功能,以及我们如何处理这些难点问题。


机器学习平台架构应该长什么样

目前主要有两大类的机器学习平台架构方向:基于Kubernetes与Docker,容器粒度的任务分配和组合,典型的如Kubeflow;基于成熟的大数据处理设施和资源调度(如Yarn)设施的机器学习平台,典型的如Hadoop的Submarine项目。两种方案有着各自擅长的场景,比如Kubernetes可以提供成熟稳健的GPU容器分配和调度,而基于大数据分析的架构则更方便进行数据的存储和处理。当然,不同架构之间的界限也不是泾渭分明的,例如有TensorflowOnSpark、Yarn调度GPU容器等项目。而本文将重点放在基于Kubernetes的机器学习平台架构。

在Kubernetes + nvidia-docker + nvidia-device-plugin这个k8s使能GPU调度的原理上,我们有一篇文章进行了详细的介绍。本文将重点放在通用的架构设计和建设难点的介绍上。

IBM在前两年建设了DLAAS(Deep-Learning-as-a-Service)平台FfDL,并在两篇文章中[1,2]详细阐释了他们FfDL平台的架构设计。在这两篇文章中,IBM提出了一个可供参考的较务实的机器学习平台架构。我们会以这个架构理论来讨论机器学习平台的设计。在IBM的论文中,将机器学习平台的设计难点总结为以下几个方面:

  1. 容错恢复能力:平台需要具备一定的容错恢复能力。在实际使用中,硬件软件层和任务调度层等,都可能出现错误,那么在出错情况下有条件的恢复训练就非常重要

  2. 算力的弹性扩展:平台要能够提供横向的算力扩展,甚至能够默认使用无限的虚拟资源的能力。比如可以让任务使用任意多GPU的能力

  3. 可观察性:机器学习平台应该有相应的API能够让用户获取任务的状态和监控

  4. 安全性:应该有足够安全的多用户多租户资源隔离手段

  5. 分布式训练能力:平台需要有稳健的、足够加速比的分布式训练技术,这就对节点间通信等提出巨大挑战

在文章[1]中,IBM给出了在他们建设FfDL的过程中,为解决以上技术难点而采用的技术架构,具体的架构如下图所示:

(FfDL架构图,图片来源[2])

在IBM给出的设计中,基本围绕着基于Kubernetes的微服务架构。上图是该架构的主要组件,我们简单介绍下该架构的特点和功能:

  • 可扩展和容错:采用基于K8s的微服务架构,并且系统服务都是无状态的,尽量减少组件间的耦合。单个组件和服务的失败可以快速重启和恢复,并且当单个服务负载过高可以实现横向的服务实例扩容

  • RESTful Api:统一的Api网关,管理所有对外公布的请求,并且管理请求的权限和认证。基于完备的Rest Api,可以快速构建CLI工具、各语言SDK和Web UI等用户界面

  • Trainer:Trainer服务是任务初始化、排队和创建服务,它将任务请求配置持久化进数据库、设定任务UUID等,并通过调用任务生命周期管理服务(LCM)来对任务的任命周期进行管理

  • LCM(LifecycleManager)和Learner:LCM通过调用k8s的api-server(通过client-go等k8s客户端)实现实际的任务创建、Pending和终止管理等任务。比如,当接收到Trainer服务的任务创建请求时,LCM会将这些任务拼装成k8s的实际的任务的形式,通常是一些pod的组合的Deployment和Job,再调用client-go将这些任务调度进k8s集群并维护相应的状态。Learner的概念则是这些Pod中的实际的训练任务的pod,在一个分布式训练的任务中,除了Learner,还可能有参数服务器的Pod,而这些Pod间的具体连接方式则是通过任务的内在配置进行设置。除了Learner和参数服务器Pod,还会有一个Pod负责任务状态的监控,以实时的更新任务的状态。

  • K8s-Cluster-Manager:基本上是一个支持GPU资源调度和任务优先级的k8s扩展,以FIFO的方式管理任务队列

  • 基于Etcd的任务状态同步:使用Etcd数据库实时记录任务的状态,比如,Job Monitor会跟踪Learner的状态并将其同步进Etcd,此后如果训练任务发生失败等故障,可以根据Etcd的状态进行恢复和同步。

  • Checkpoint & Resume:暂停和恢复是一个非常难实现的功能,但是用户却很希望有这个功能。Ffdl的设计中对于恢复机制的实现主要是依赖AI框架本身checkpoint的机制。比如,对于部分支持Checkpoint机制的框架(如Tensorflow和Caffe),任务运行的时候将checkpoint输出到持久存储,并将其它的任务配置和状态信息写入Etcd,这样就提供了所有的持久状态。当任务Resume时将使用Etcd中的任务配置和挂载模型checkpoint卷,实现任务的暂存和恢复。

  • 错误处理:如果一个Learner的Pod发生故障,Job monitor将会检测到这个错误,并触发LCM服务进行进一步的错误处理,比如视错误原因的不同,可以重启Learner的pod,或是终止整个任务。

  • 安全性:每个用户的任务都会运行在独立的容器中并保证容器不会开启Privileged权限,并设置独立的pod网络访问策略,比如阻止所有访问pod的网络权限,所有的数据交互都是通过共享的持久卷实现。

  • AI框架插件:支持增加和拓展新的AI框架

除了以上方面,存储的设计在机器学习平台中至关重要。在FfDL设计中,包含两个分布式存储,对象存储和基于SSD的NFS服务,对象存储存放大部分冷数据,而NFS作为一个热数据的缓存介质,以PVC的方式集成到K8s的Pod中。

在云化产品中,是非常推荐对象存储的。但是对象存储在使用上需要特定的客户端工具,这在使用上会给用户带来一定的不便。如果能将对象存储以兼容POSIX文件系统标准的挂载卷方式集成进pod,会在用户使用上带来很大的便利性。IBM的FfDL正是基于这样的考虑,发起一个项目对兼容s3的对象存储开发k8s的storage-plugin:https://github.com/IBM/ibmcloud-object-storage-plugin。在这个项目中,通过k8s的volume-plugin的方式增加了对s3对象存储的挂载卷的支持,从而能够让pod像挂载NFS一样挂载对象存储,并支持完备的POSIX文件操作接口,用户使用上就如同本地文件系统一样。但遗憾的是,基于s3fs-fuse的方式开发的volume-plugin,在读写性能上都有较大的影响。

我们将FfDL的简单介绍止于此处,有兴趣的读者可以详细阅读文末的两篇关于FfDL的论文。总的来说,FfDL是一个比较务实和周全的设计,并且为机器学习平台的设计和建设提供了一些理论上的指导,我们帆一尚行的iGear平台也从中汲取了一定的营养。但是客观的说,FfDL的设计也有很多值得改进的地方。带着这些思考以及对机器学习平台的一般性设计和难点的初步认知,我们可以来审视一下我们的iGear平台的架构和设计。


帆一尚行iGear平台架构概述

帆一尚行机器学习平台iGear是为了服务上汽集团内部的AI计算服务需求而开发的全流程机器学习平台。iGear包含数据管理平台、标注平台和训练平台三大部分,本文重点聚焦在训练平台上。

建设这样的训练平台我们面临很大的技术挑战,例如海量数据的存储和管理,数据预处理,异构计算的调度和分配,任务的执行和容错,IO的高效传输,GPU性能的优化,数据集的动态挂载,模型的压缩和发布等等。以下是我们训练平台的整体架构:


(帆一尚行训练平台架构)

我们总体上也是采用基于Kubernetes的微服务架构,平台服务和机器学习任务都是用k8s进行管理。k8s管理着异构的计算资源(CPU/GPU/Arm等),特别的,通过nvidia-device-plugin和nvidia-docker实现GPU的调度。在总的架构上,我们抽象出专门的training-service负责构建主要的web后端,它基于Spring Boot,职责包括任务的创建、配置和生命周期管理。另外开发了一个使用Golang的k8s-scheduler-service的轻状态的服务,它接收来自training-service的任务信息,负责与k8s交互,维护任务队列,监听k8s资源,管理计时器,执行实际的任务调度等。此外,还会有多个跨语言的微服务和辅助组件,例如数据集转换服务,日志处理服务,init-container任务预处理组件,Tensorboard服务,容器镜像checkpoint恢复服务,TensorRT服务,sidecar挂载服务等等。各个服务相互协作,稳健地保障复杂任务调度管理的需求。

因为内容比较多,我们会挑一些重要的组件和问题加以说明。在下面的各章节中,我们会挑相关重点介绍我们的架构和功能点,以及对应于前文所说的通常的机器学习平台面临的问题,我们iGear平台是怎么样去解决的。


iGear平台的算力调度系统

算力调度系统是机器学习平台的核心,如何调度GPU任务、如何管理异构的计算资源、在资源不足的情况下如何管理不同资源规格的任务队列和优先级等等。

  • 异构计算资源管理

异构计算的概念经常被提及,简单来说异构计算包含三部分:异构的机器(如Intel CPU\ARM\Nvidia-GPU\AMD-GPU\FPGA等服务器和计算硬件的组合);将这些异构的机器连接在一起的网络;在此之上支持异构计算的软件。随着容器时代的来临,可以将(部分)异构计算的场景简化统一到容器调度的场景,而Kubernetes则成为了调度容器任务的事实标准。目前最重要的计算介质是Nvidia GPU的调度,一台GPU服务器通常安装有8块GPU卡,通过nvidia-docker和device-plugin机制,我们可以实现容器和GPU的绑定。所以我们后面讨论的GPU的调度和管理,实际上是指创建绑定了GPU卡的容器任务的调度。

我们使用K8s管理着包括V100卡的DGX-1系列服务器、P40服务器、P100服务器和Intel CPU服务器的异构计算集群,只涉及容器粒度的Nvidia-GPU和Intel-CPU的异构计算任务调度。后续我们也在考虑支持ARM架构的服务器和AMD的GPU芯片。特别值得说明的是,AMD于2017年发布了ROCm平台,开始为开发者提供深度学习支持,Tensorflow官方已经可以支持ROCm,社区也有支持ROCm的容器方案和k8s-device-plugin,这为我们进一步支持AMD的显卡提供了极大的便利。

在iGear平台,用户可以根据需要去使用不同的计算资源,比如支持不同GPU卡数量的容器,不同的CPU和内存配额等。同时,我们在基础容器上面做了大量优化。例如,我们会根据不同的GPU型号和数量为容器配备匹配的CPU和Memory资源;对部分特殊的容器环境提供更大的Shared Memory,以保证pytorch的多进程数据加载的需求;计算节点和存储节点的网络优化等等。

  • 集群调度系统

帆一尚行的iGear平台整体上是基于Kubernetes的架构,技术栈上是基于Kubernetes做调度,nvidia-docker作为支持GPU的容器运行时,nvidia-device-plugin作为将GPU资源纳入k8s管理调度的device-plugin插件。我们在上一篇文章中对其中的机制有一个较为详尽的介绍。此外,为了最大化利用GPU资源,我们对社区的nvidia-device-plugin做了一些改动,以支持更高的GPU复用率和更好的通道亲和性组合。这样会使得在某些场景下可以创建远比GPU个数多的GPU容器环境,让多个容器可以按照一定策略共享相同的GPU卡。并且做单容器多卡绑定的时候能够将高通道连接的(如Nvlink)卡绑定在同一个容器中,使得训练性能获得较大的提升。

基于k8s我们很容易管理调度任务,我们制定了一些策略在某些条件下恢复出错的容器、挂起和恢复某些任务,以及服务的实例的横向拓展等等。另外基于k8s的监控设施,iGear平台有很好的可观测性,任务的出错、终止、恢复等都可以在不同的粒度上得到监控和处理。

此外,我们并没有像FfDL一样通过拓展k8s-scheduler的方式集成FIFO的任务队列,而是在k8s系统上维护用户态的任务队列,这样可以更加方便地管理复杂的任务,比如具有不同资源规格和任务优先级的任务。

  • 任务队列管理

相比于大量的训练计算需求,算力永远是不够的。这个时候需要对用户提交的任务有一个队列和优先级的管理,而不能完全依赖Kubernetes的队列管理。我们平台会幂增的支持几种资源规格的任务,比如分别挂载1,2,4,8张GPU卡的训练任务。当然除了GPU任务也有CPU任务的队列,可以设置不同的cpu和内存资源。对于同一种资源规格,都会有一个优先队列,当有相应规格的训练任务提交时,会先进入相应的队列,当然,因为是优先级队列,我们会给插入的任务进行评分,当任务的优先级比较高,会优先获得执行的权利。

iGear的k8s-scheduler服务会有一个统计模块实时的统计集群中相应规格的可用GPU资源的数量,当集群中该资源规格的GPU尚有空余,那么排在最前面的任务会优先获得执行的权利,这个时候它会被从队列中弹出并调度进K8s,当然会有跟踪机制去跟踪任务的状态。当集群中没有可用的该规格的资源时,任务便会在任务队列里等待。

在具体的GPU调度上还有个很棘手的问题:因为用户的任务可以指定不同资源规格的,有的容器绑定3块卡,有的绑定1块,那么随着大量不同生命周期的任务被创建,有可能主机上的任务会有很多"空隙",比如node1,node2各有一个gpu卡在赋闲,队列中有一个2卡的任务却得不到执行,因为集群中赋闲的两张卡并不在同一台主机上。我们解决的方案是给集群中的主机进行分片,会给同一片的机器打上特定资源规格的Label,并且在调度的时候会将不同的资源规格调度到指定的主机上,此外资源规格按照幂增的1,2,4,8四种规格,有点类似于编译器中常见的堆分配的策略。在用户使用的过程中,我们会根据任务资源的统计在宏观上调节不同资源主机的数量,最大化利用硬件资源。

当任务在等待时,我们并没有采用轮询的方式去判断集群资源什么时候可用,因为轮询的方式非常耗费资源,尤其是全量查询一下k8s集群的资源状态。得益于K8s原生的Informer机制,我们可以监听pod、job和node的状态,当有这些资源的状态变化时,Informer会捕捉到相应的状态并触发我们注册的回调函数。Informer的简单原理如下:

(图片来源:http://wsfdl.com)

Informer的工作流程实际上是一个基于事件和回调的消息通知机制:通过k8s的apiserver的list和watch接口来监听资源。具体的,通过list调用来获取集群中相应资源列表,然后调用watch这个长连接接口监听资源的状态的变更事件。当watch的资源有变化时,会将结果放入一个FIFO队列里,队列的出口处的goroutine会依次拿出这些事件,并调用注册在Informer的回调函数执行相应的逻辑。在Informer的回调机制中,支持资源的增加(AddFunc),更新(UpdateFunc)和删除(DeleteFunc)的回调。另外,图中的MapStore是一个缓存组件,Informer使用它来缓存list查出的资源信息,这样可以减少对apiserver的调用并提高查询效率。

在业务上举个例子,比如当任务pod销毁时,我们希望k8s-scheduler执行一些任务回收的操作。那么我们可以注册Informer的DeleteFunc的回调函数,里面进行相应的任务回收操作,并触发scheduler进行下一轮任务的分配。通过这种方式,可以非常优雅的同步集群资源和队列的状态。


iGear平台的存储设计

存储在机器学习平台中非常重要,因为机器学习往往包含海量大小的数据集,数据的传输性能和稳定性非常重要。正如FfDL的架构,通常机器学习平台都会使用多级存储的方式,比如冷数据存放在容量巨大、价格便宜的对象存储中,而进一步的数据处理会存放在离训练服务器较近的NAS存储上。

我们也同样使用多级存储的方式,冷数据存储在兼容s3协议的对象存储中,热数据则放在具有高性能的分布式NAS,但同时我们增加了一个本机的缓存,这能够在训练任务中极大提升IO性能。下图是我们的多级存储架构:

(iGear多级存储架构)

最理想的存储是让用户忘记存储的存在,用户使用上就像使用本机存储一样,但是很难做到。尤其是我们比较依赖的对象存储,使得在用户使用上会有习惯上的问题。在某些场景下,我们依然面临用户希望直接挂载对象存储的强烈需求。和FfDL一样,我们采用过IBM的volume-plugin的方式,通过拓展使得s3对象存储能够像普通持久卷一样在pod中挂载,但是性能测试的结果发现这种方式性能太差,我们不得不调研其它的方式。最终我们选定了Goofys(https://github.com/kahing/goofys)这个开源项目。总的来说,Goofys放弃对Posix所有标准的支持,而是仅支持部分(比如追加写的接口就不支持),但是能够提供远比fuse方式更快的读写性能。比如,下图是官方给出的关于Goofys和s3fs与riofs的性能对比:


(图片来源:https://github.com/kahing/goofys)

而我们自己的测试也基本支持这个结论。可以看出Goofys相比于其它完整实现fuse的挂载工具有着3-4倍的性能提升,Goofys的官网有更详细的性能对比。此外Goofys还有相应的Cache工具catfs(https://github.com/kahing/catfs),能够提供较好支持的本地缓存。

在使用Goofys的方式上,我们为了绝对的数据安全,采用了kubernetes的sidecar的模式:在一个pod中创建两个容器,一个主容器,一个sidecar容器,所有的对象存储的挂载行为在sidecar中进行,并通过目录共享的方式提供给主容器。我们将对主机有较大权限的功能全部放在sidecar中,并对sidecar容器进行安全的强管控,安装最小的必要系统和分享最小权限的卷给主容器,而主容器中则关闭所有高访问权限的flag。

基于sidecar的架构,我们还提供了一个非常好的特性:动态卷挂载,即可以在不重启容器的前提下实现动态的数据集挂载和卸载。其实现的方式是通过更新一个跟任务相关的k8s的configmap,configmap支持以文件的形式挂载进sidecar容器中,并且当configmap有更新时,挂载进pod的文件也会刷新且不会重启容器。这样我们就可以在sidecar中监听来自configmap的变更,而对数据集的挂载进行主动的更新,实现数据集的动态挂载。

对于分布式NFS的支持在k8s中则要简单很多,我们通过k8s的StorageClass实现动态的PV/PVC的创建和挂载,并实现IO流量控制。此外基于CacheFs,我们可以实现很好的NFS的本地缓存的支持,极大程度上提升IO性能。


iGear平台的用户交互

我们在建设iGear的过程中,面临的一个非常头疼的问题就是用户的交互形式。有相当一部分用户并不习惯云化的工作方式,他们习惯于桌面环境的开发模式。于是如何将一个云化的机器学习平台让用户广泛的接受,并提供比本地更为高效的开发效率,一直是iGear平台建设过程中的重中之重。

  • 数据集的处理方式

在标注数据集到可以直接进行训练的数据集(我们称为训练数据集)之间,往往还存在着很大的距离。根据标注工具的不同,有可能标注数据集是COCO格式且没有经过压缩的,而训练数据集则需要VOC数据集而且压缩成LMDB的格式。

所以数据的预处理和各种格式的规范化在一个平台产品中尤其重要。在iGear中,我们做了很多工作在填满标注数据集和训练数据集之间的鸿沟:比如我们支持主流数据集格式的生成(COCO,VOC等),支持图片的各种resize和裁剪算法,支持训练集、测试集、验证集的划分,还可以对生成的数据集进行二进制格式的压缩,如压缩成LMDB和TfRecord等格式。此外,我们还可以基于不同数据集进行组合和拼装,能够很大自由度的进行数据集的创建。

  • 交互模式

在使用界面上,我们提供交互模式和任务模式两种形式。交互模式定位在给用户提供一个方便的算法开发和调试环境,而任务模式则是当用户代码成熟后,可以执行进行训练并跟踪状态的自动化、流程化环境。

交互模式在创建的时候用户可以指定想要的资源数量(如GPU数量),也可以指定运行的环境,比如Tensorflow-1.5或者Pytorch-1.0等等。我们平台内置了所有主流的AI框架和版本。交互模式的容器在k8s集群中以Deployment的方式存在。交互模式是最重用的用户界面,用户通过创建交互模式进行算法的开发,所以它对使用的功能性和灵活性要求很高,我们需要提供功能完备且易用的交互式开发环境。目前主流的机器学习平台主要会使用jupyter来进行用户的交互,但是原生的jupyter并不是特别友好,尤其是目录和文件的操作。适逢Jupyterlab这个项目发布了稳定版本,而Jupyterlab被认为是下一代的Jupyter,它有着非常友好的用户界面,尤其是左侧的文件操作组件,基本上是一个非常好用的WebIDE。我们经过比较细致的测试,决定集成JupyterLab。下图是iGear平台的交互式环境的JupyterLab界面:

(iGear的交互式环境操作界面)

在原生的JupyterLab基础上,我们还进行了大量的用户使用上的优化,比如JupyterLab原生的Web Shell非常难用,我们替换成zsh并内置广受欢迎的oh-my-zsh,用户反馈他们的效率由此提升很多。另外,为了防止用户忘记关闭容器而导致GPU无效的占用,我们会使用计时器去管理交互模式的任务,用户可以指定和随时更改容器使用的时间,当设定时间到期后,任务会被自动终止。当然,也可以设置永久运行的任务以应对需要常驻的交互式任务。交互式环境中用户常常需要可以安装一些定制的软件包,我们在容器中允许用户通过pip和apt安装相应的python和ubuntu的软件包,在使用上减少用户的限制。另外还支持TensorBoard的集成,方便用户使用TensorBoard进行训练结果的渲染和网络结构的查看。

还有一个非常常见的需求是用户希望在容器中随时能够切换和挂载任意数据集,通过前文介绍的Sidecar的方式,我们实现了用户的动态数据集挂载,使用户可以使用任何输入环境。在交互式环境中还提供持久存储,包括单个任务的持久卷,单个用户但是可以跨任务共享的持久卷,以及组内共享的持久卷等等。除此此外,还提供容器粒度的资源监控,方便查看容器中的GPU和显存使用量,这里就不再赘述了。

  • 任务模式

任务模式则是当算法成熟以后,以任务的方式托管在我们平台的训练执行方式。创建任务模式时,可以指定不同的训练任务,并挂载不同的数据集,设置相应的GPU资源使用量,然后提交执行,便可以将任务提交到任务队列,当集群中有资源时,该任务将会被执行。任务模式也会提供一些自定义参数的配置权限给用户,比如算法的所有超参数可以暴露出来给用户进行设置。

任务模式以K8s中的Job的形式存在,当任务主进程结束后,k8s会自动释放资源结束任务并回收相关资源。我们也会在Informer中监听 相应任务Pod的退出状态,并调用相应的回调去更新任务的状态。

任务模式中很重要的一部分就是前面提到的任务的故障恢复和终止。在任务模式中,我们同样采用sidecar的模式,会在主容器之外配置一个sidecar的容器,并共享部分目录。在sidecar中会管理该任务的挂载的数据集和一些输出的持久卷。任务重要的一些输出如模型的checkpoint等都会被存入持久目录,以方便当任务重启或恢复的时候可以还原状态。另外,主容器的输入日志也会共享到sidecar中,sidecar容器中有监听改状态日志的程序,并根据主容器的状态决定整个任务是否终止或恢复等。

另一个非常好用的功能是任务模式的克隆功能:当一个训练任务结束后,用户很有可能希望基于训练得出的模型的基础上换一个数据集进行继续训练。克隆模式可以将用户训练完的模型和配置克隆出来,并替换数据集进行进一步的训练操作,实现基本的迁移学习的功能。

在训练任务的输出上,我们提供任务结果的渲染界面,通过解析训练日志,能够将任务的Loss曲线和精度曲线等渲染出来。也提供文本日志的展示和GPU资源的监控和展示。而对于Tensorflow,我们更推荐使用Tensorboard进行渲染,在任务模式中也对此有很好的支持。

在底层调度上,我们会将训练任务按照优先级进行排队,当系统中有资源空出的时候任务会被调度执行。当资源比较充足时,通过我们定制的k8s-device-plugin,底层的调度还会考虑GPU间的通道亲和性。

对于训练完成的任务,iGear提供模型的版本管理和进一步的模型导出管理,并集成TensorRT对输出模型进行压缩和导出部署的开发。


iGear平台的其它特性

iGear平台还有比较多的特性,难以再一一赘述。我们最后挑选几个在机器学习平台中比较重要的几个功能、特性和安全性保障的介绍。

  • Checkpoint机制

不管是交互模式还是任务模式,基于容器的任务对于恢复环境这个需求都会显得比较棘手。广义的checkpoint机制是指能够恢复用户的开发环境或者继续用户的训练流程和结果。而这两个定义都有一定的挑战性:恢复整个开发环境与容器的无状态的哲学有点背道而驰;恢复训练的流程则依赖于框架本身的checkpoint机制。

对于恢复训练流程,类似于Ffdl,我们也依赖框架的checkpoint机制对任务进行保存,比如,我们会将任务模式的caffe模型输出的checkpoint保存在持久卷里,当恢复任务的时候可以重新挂载以实现任务进度的恢复。

对于完全恢复用户的环境,我们发现用户也有比较强烈的需求。比如,在交互模式中,用户手动安装了一些软件包,并在非持久目录下创建了一些文件,那么当任务结束和超时退出的时候,用户希望彻底的恢复所有的环境,但是通常来说容器一旦退出后并不会保存这些环境。

尽管有一些容器的checkpoint恢复机制,但是大都不太成熟,尤其是对nvidia-docker。权衡再三,我们决定采用docker原生的commit机制进行容器的保存和恢复。在具体的实现上,我们会在每个节点上部署一个DaemonSet,而在DaemonSet中通过共享主机侧的Docker.sock,实际上是主机上的docker daemon的一个客户端。这样当有保存镜像的请求或者交互式任务过期的时候,会调用该Pod部署的节点上相应的DaemonSet的docker commit将容器的改动commit成一个镜像并push到Harbor Registry中保存下来,下次任务恢复的时候则拉取相应的镜像进行恢复。

当然这种方式也会引入一些头疼的问题,比如docker的AUFS的文件系统的机制是每次在容器里面的任何操作实际上是在该文件系统上再写入一层。所以会导致commit用户的运行镜像通常会得到一个比较大的镜像。我们权衡再三,决定还是以这样的方式提供用户的恢复机制,但是每个保存的镜像只会保存15天,15天后这些镜像会被调用Harbor的相应接口进行删除。防止镜像的数量和体积膨胀到无法维护的状态。

  • 安全性

定位成一个云化的机器学习平台,并且支持多租户,iGear一直把安全性放在头等重要的位置。在安全性上我们提供非常严格的安全隔离。比如iGear为每一个租户提供单独的Namespace进行资源和网络的隔离;在api层面有着严格的细粒度校验和ACL控制;存储上也会为每个租户维护单独的Bucket,实现存储上的租户隔离;在一般的辅助容器中,遵循最小权限接口,没有任何多余和不安全的端口;严格的渗透测试和攻击测试等等。除此之外,帆一尚行也有一只非常专业的安全团队为公司的每个产品保驾护航。

  • 拓展性

作为一个平台性产品,拓展性非常重要。比如除了内置的框架和模型,用户很可能需要快速上线一个完全新的框架模型。这个时候我们需要提供一个灵活的架构非常方便的快速集成定制的模型。

无论是交互模式、训练模式或是数据预处理组件,iGear都提供比较好的拓展性。比如对于交互模式的镜像,我们会提供一个标准,用户在增加自定义框架时,只要符合我们的标准,该镜像便可以发布在iGear平台并且和内置的镜像一样享有平台的各项内置功能,而这套标准接口本身也是非常简单易用的,目前可以做到在半天内就可以上线用户提供的所有框架模型。对于任务模式,我们提供了相应的开发SDK,这套SDK提供包括日志的规范和渲染、任务的终止和异常、训练的进度、模型的管理等等,通过SDK可以方便地将训练模型集成到iGear平台。

通过比较好的扩展性,我们希望打造一个通用的机器学习平台,对用户的使用框架、数据集格式等没有任何限制,并且让任何算法都可以快速集成到iGear。

总结

本文侧重在基于Kubernetes的机器学习平台的通用架构设计的探讨。通过IBM的Ffdl的理论探讨,重点介绍机器学习平台的通用组件和主要挑战。基于这些理论上的挑战,我们也阐释了如何务实地建设一个稳健的机器学习平台。我们简要介绍了iGear训练平台部分的功能和组件,并附带一些我们在建设机器学习平台的过程中的思考。


参考文献

[1]IBM Deep Learning Service   (https://arxiv.org/abs/1709.05871)

[2]Scalable Multi Framework Multi Tenant Lifecycle Management of Deep Learning Training Jobs


关于我们

帆一尚行AI基础开发团队:AI时代不光需要一流算法科学家,更需要一流的基础平台,还需要强大的工程化落地能力。我们是一支致力于打造一流AI工作平台的团队,同时也积极承担AI赋能业务场景的工程化落地实践。Geek、敏捷、平等、自主已经深入团队文化基因。一线大厂、顶级高校并不是我们愿意展示的团队成员标签,我们更在乎的是团队对具体经济活动输出的价值。


作者介绍

任如意:AI系统架构师,致力于构建普惠的AI基础设施。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存